package com.zhichat.infrastructure.redis.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.zhichat.infrastructure.redis.RedisRepository;
import com.zhichat.infrastructure.redis.rediskey.Key;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisRepositoryImpl implements RedisRepository {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedisTemplate<String, Object> redisTemplateString;

    @Override
    public void set(String key, String value) {
        if (StrUtil.isBlank(key)) return;
        stringRedisTemplate.opsForValue().set(key, value);
    }

    @Override
    public void set(String key, String value, long timeout, TimeUnit unit) {
        if (StrUtil.isBlank(key)) return;
        stringRedisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    @Override
    public void set(String key, String value, Duration duration) {
        if (StrUtil.isBlank(key)) return;
        stringRedisTemplate.opsForValue().set(key, value, duration);
    }

    @Override
    public void set(Key key, String value, Object... params) {
        if (key == null) return;
        if (StrUtil.isNotEmpty(value)) {
            set(key.getKey(params), value, key.duration(params));
        }
    }

    @Override
    public <T> void set(Key key, T t, Object... params) {
        if (key == null) return;
        if (t != null) {
            set(key, JSONObject.toJSONString(t), params);
            log.debug("==> Set Cache , {}", key.getKey(params));
        }
    }

    @Override
    public <T> void set(Key key, List<T> list, Object... params) {
        if (key == null) return;
        if (list == null) {
            list = Collections.emptyList();
        }
        set(key, JSONObject.toJSONString(list), params);
    }

    @Override
    public String get(String key) {
        if (StrUtil.isNotEmpty(key)) {
            return stringRedisTemplate.opsForValue().get(key);
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T get(String key, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) return null;
        String value = get(key);
        if (StrUtil.isEmpty(value)) return null;
        try {
            if (String.class.equals(clazz)) {
                return (T) value;
            }
            return JSONObject.parseObject(value, clazz, Feature.OrderedField);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> getList(String key, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) return null;
        String value = get(key);
        if (StrUtil.isEmpty(value)) return null;
        try {
            return JSONObject.parseArray(value, clazz);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String get(Key key, Object... params) {
        if (key == null) {
            return null;
        }
        return get(key.getKey(params));
    }

    @Override
    public <T> T get(Key key, Class<T> clazz, Object... params) {
        if (key == null) return null;
        return get(key.getKey(params), clazz);
    }

    @Override
    public <T> List<T> getList(Key key, Class<T> clazz, Object... params) {
        if (key == null) return null;
        return getList(key.getKey(params), clazz);
    }

    @Override
    public long incr(String key) {
        if (key == null) {
            return 0L;
        }
        Long l = stringRedisTemplate.opsForValue().increment(key);
        if (l == null) {
            return 0L;
        }
        return l;
    }

    @Override
    public long incr(Key key, Object... params) {
        if (key == null) {
            return 0L;
        }
        return incr(key.getKey(params));
    }

    @Override
    public void lazyFree(String key) {
        if (StrUtil.isBlank(key)) return;
        stringRedisTemplate.unlink(key);
        redisTemplateString.unlink(key);
    }

    @Override
    public void del(String key) {
        if (StrUtil.isBlank(key)) {
            return;
        }
        lazyFree(key);
        log.debug("==> Delete Cache, {}", key);
    }

    @Override
    public void del(Key key, Object... params) {
        if (key == null) return;
        del(key.getKey(params));
    }

    @Override
    public void expire(String key, final long timeout, final TimeUnit unit) {
        redisTemplateString.expire(key, timeout, unit);
    }

    @Override
    public void expire(String key, Duration duration) {
        redisTemplateString.expire(key, duration);
    }

    @Override
    public void expire(Key key, Object... params) {
        if (key == null) return;
        expire(key.getKey(params), key.duration(params));
    }

    @Override
    public void expireString(String key, final long timeout, final TimeUnit unit) {
        stringRedisTemplate.expire(key, timeout, unit);
    }

    @Override
    public void expireString(String key, Duration duration) {
        stringRedisTemplate.expire(key, duration);
    }

    @Override
    public void expireString(Key key, Object... params) {
        if (key == null) return;
        expire(key.getKey(params), key.duration(params));
    }

    @Override
    public <T> void leftPush(String key, T t) {
        if (StrUtil.isEmpty(key)) return;
        redisTemplateString.opsForList().leftPush(key, t);
    }

    @Override
    public <T> void leftPushAll(String key, Collection<T> list) {
        if (StrUtil.isEmpty(key)) return;
        if (list == null) {
            list = Collections.emptyList();
        }
        redisTemplateString.opsForList().leftPushAll(key, list);
    }

    @Override
    public <T> void leftPush(String key, T t, final long timeout, final TimeUnit unit) {
        leftPush(key, t);
        expire(key, timeout, unit);
    }

    @Override
    public <T> void leftPush(String key, T t, Duration duration) {
        leftPush(key, t);
        expire(key, duration);
    }

    @Override
    public <T> void leftPushAll(String key, Collection<T> list, final long timeout, final TimeUnit unit) {
        leftPushAll(key, list);
        expire(key, timeout, unit);
    }

    @Override
    public <T> void leftPushAll(String key, Collection<T> list, Duration duration) {
        leftPushAll(key, list);
        expire(key, duration);
    }

    @Override
    public <T> void leftPush(Key key, T t, boolean isExpire, Object... params) {
        if (key == null) return;
        if (isExpire) {
            leftPush(key.getKey(params), t, key.duration(params));
        } else {
            leftPush(key.getKey(params), t);
        }
    }

    @Override
    public <T> void leftPushAll(Key key, Collection<T> list, boolean isExpire, Object... params) {
        if (key == null) return;
        if (isExpire) {
            leftPushAll(key.getKey(params), list, key.duration(params));
        } else {
            leftPushAll(key.getKey(params), list);
        }

    }

    @Override
    public <T> void leftPush(Key key, T t, Object... params) {
        leftPush(key, t, true, params);
    }

    @Override
    public <T> void leftPushAll(Key key, Collection<T> list, Object... params) {
        leftPushAll(key, list, true, params);
    }

    @Override
    public <T> void rightPush(String key, T t) {
        if (StrUtil.isEmpty(key)) return;
        redisTemplateString.opsForList().rightPush(key, t);
    }

    @Override
    public <T> void rightPushAll(String key, Collection<T> list) {
        if (StrUtil.isEmpty(key)) return;
        if (list == null) {
            list = Collections.emptyList();
        }
        redisTemplateString.opsForList().rightPushAll(key, list);
    }

    @Override
    public <T> void rightPush(String key, T t, final long timeout, final TimeUnit unit) {
        rightPush(key, t);
        expire(key, timeout, unit);
    }

    @Override
    public <T> void rightPush(String key, T t, Duration duration) {
        rightPush(key, t);
        expire(key, duration);
    }

    @Override
    public <T> void rightPushAll(String key, Collection<T> list, final long timeout, final TimeUnit unit) {
        rightPushAll(key, list);
        expire(key, timeout, unit);
    }

    @Override
    public <T> void rightPushAll(String key, Collection<T> list, Duration duration) {
        rightPushAll(key, list);
        expire(key, duration);
    }

    @Override
    public <T> void rightPush(Key key, T t, boolean isExpire, Object... params) {
        if (key == null) return;
        if (isExpire) {
            rightPush(key.getKey(params), t, key.duration(params));
        } else {
            rightPush(key.getKey(params), t);
        }
    }

    @Override
    public <T> void rightPushAll(Key key, Collection<T> list, boolean isExpire, Object... params) {
        if (key == null) return;
        if (isExpire) {
            rightPushAll(key.getKey(params), list, key.duration(params));
        } else {
            rightPushAll(key.getKey(params), list);
        }
    }

    @Override
    public <T> void rightPush(Key key, T t, Object... params) {
        rightPush(key, t, true, params);
    }

    @Override
    public <T> void rightPushAll(Key key, Collection<T> list, Object... params) {
        rightPushAll(key, list, true, params);
    }

    @Override
    public <T> T leftPop(String key, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) {
            return null;
        }
        Object object = redisTemplateString.opsForList().leftPop(key);
        if (object != null) {
            return JSONObject.parseObject(JSONObject.toJSONString(object), clazz);
        }
        return null;
    }

    @Override
    public <T> T rightPop(String key, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) {
            return null;
        }
        Object object = redisTemplateString.opsForList().rightPop(key);
        if (object != null) {
            return JSONObject.parseObject(JSONObject.toJSONString(object), clazz);
        }
        return null;
    }

    @Override
    public <T> T leftPop(Key key, Class<T> clazz, Object... params) {
        if (key == null) {
            return null;
        }
        return leftPop(key.getKey(params), clazz);
    }

    @Override
    public <T> T rightPop(Key key, Class<T> clazz, Object... params) {
        if (key == null) {
            return null;
        }
        return rightPop(key.getKey(params), clazz);
    }

    @Override
    public Long size(String key) {
        if (StrUtil.isEmpty(key)) {
            return null;
        }
        return redisTemplateString.opsForList().size(key);
    }

    @Override
    public Long size(Key key, Object... params) {
        if (key == null) {
            return null;
        }
        return size(key.getKey(params));
    }

    @Override
    public <T> List<T> range(String key, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) {
            return null;
        }
        List<Object> list = redisTemplateString.opsForList().range(key, 0, -1);
        if (CollectionUtils.isNotEmpty(list)) {
            return JSONArray.parseArray(JSONObject.toJSONString(list), clazz);
        }
        return null;
    }

    @Override
    public <T> List<T> range(String key, Class<T> clazz, int end) {
        Long size = size(key);
        if (size == null || size < 1) {
            return null;
        }
        if (size < end || end < 0) {
            return range(key, clazz);
        }
        List<Object> list = redisTemplateString.opsForList().range(key, 0, end);
        if (CollectionUtils.isNotEmpty(list)) {
            return JSONArray.parseArray(JSONObject.toJSONString(list), clazz);
        }
        return null;
    }

    @Override
    public <T> List<T> range(Key key, Class<T> clazz, Object... params) {
        if (key == null) {
            return null;
        }
        List<Object> list = redisTemplateString.opsForList().range(key.getKey(params), 0, -1);
        if (CollectionUtils.isNotEmpty(list)) {
            return JSONArray.parseArray(JSONObject.toJSONString(list), clazz);
        }
        return null;
    }

    @Override
    public <T> List<T> range(Key key, Class<T> clazz, int end, Object... params) {
        if (key == null) {
            return null;
        }
        return range(key.getKey(params), clazz, end);
    }

    @Override
    public <T> void lRemove(String key, T t, int count) {
        if (key == null) return;
        redisTemplateString.opsForList().remove(key, count, t);
    }

    @Override
    public <T> void lRemove(Key key, T t, int count, Object... params) {
        lRemove(key.getKey(params), t, count);
    }

    @Override
    public <T> void add(String key, Set<T> t) {
        if (StrUtil.isEmpty(key)) return;
        redisTemplateString.opsForSet().add(key, t.toArray());
    }

    @Override
    public <T> void add(String key, Set<T> t, final long timeout, final TimeUnit unit) {
        if (StrUtil.isEmpty(key)) return;
        add(key, t);
        expire(key, timeout, unit);
    }

    @Override
    public <T> void add(String key, Set<T> t, Duration duration) {
        if (StrUtil.isEmpty(key)) return;
        add(key, t);
        expire(key, duration);
    }

    @Override
    public <T> void add_1(Key key, Set<T> t, Object... params) {
        if (key == null) return;
        add(key.getKey(params), t, key.duration(params));
    }

    @Override
    public <T> void add_2(Key key, Set<T> t, Object... params) {
        add(key.getKey(params), t);
    }

    @Override
    public <T> void addAll(String key, Collection<T> col) {
        if (StrUtil.isEmpty(key)) return;
        redisTemplateString.opsForSet().add(key, col.toArray());
    }

    @Override
    public <T> void addAll(String key, Collection<T> col, final long timeout, final TimeUnit unit) {
        addAll(key, col);
        expire(key, timeout, unit);
    }

    @Override
    public <T> void addAll(String key, Collection<T> col, Duration duration) {
        addAll(key, col);
        expire(key, duration);
    }

    @Override
    public <T> void addAll(Key key, Collection<T> col, boolean isExpire, Object... params) {
        if (key == null) return;
        if (isExpire) {
            addAll(key.getKey(params), col, key.duration(params));
        } else {
            addAll(key.getKey(params), col);
        }
    }

    @Override
    public <T> void addAll(Key key, Collection<T> col, Object... params) {
        addAll(key, col, true, params);
    }

    @Override
    public <T> List<T> members(String key, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) {
            return null;
        }
        Set<Object> set = redisTemplateString.opsForSet().members(key);
        if (CollectionUtils.isNotEmpty(set)) {
            return JSONArray.parseArray(JSONObject.toJSONString(set), clazz);
        }
        return null;
    }

    @Override
    public <T> List<T> members(Key key, Class<T> clazz, Object... params) {
        if (key == null) {
            return null;
        }
        return members(key.getKey(params), clazz);
    }

    @Override
    public <T> Boolean isMember(String key, T t) {
        if (StrUtil.isEmpty(key)) {
            return Boolean.FALSE;
        }
        return redisTemplateString.opsForSet().isMember(key, t);
    }

    @Override
    public <T> Boolean isMember(Key key, T t, Object... params) {
        if (key == null) {
            return Boolean.FALSE;
        }
        return isMember(key.getKey(params), t);
    }

    @Override
    public Long card(String key) {
        if (StrUtil.isEmpty(key)) {
            return 0L;
        }
        return redisTemplateString.opsForSet().size(key);
    }

    @Override
    public Long card(Key key, Object... params) {
        if (key == null) {
            return 0L;
        }
        return card(key.getKey(params));
    }

    @Override
    public <T> void sRemove(String key, Set<T> t) {
        if (StrUtil.isEmpty(key)) return;
        redisTemplateString.opsForSet().remove(key, t.toArray());
    }

    @Override
    public <T> void sRemove(Key key, Set<T> t, Object... params) {
        if (key == null) return;
        sRemove(key.getKey(params), t);
    }

    @Override
    public <T> void setOpsHash(String key, String hashKey, T value) {
        if (StrUtil.isEmpty(key)) return;
        stringRedisTemplate.opsForHash().put(key, hashKey, JSONObject.toJSONString(value));
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getOpsHash(String key, String hashKey, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) return null;
        Object object = stringRedisTemplate.opsForHash().get(key, hashKey);
        if (object == null) return null;
        if (clazz.getTypeName().equals(String.class.getTypeName())) {
            return (T) object;
        }
        try {
            return JSONObject.parseObject((String) object, clazz);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> List<T> getListOpsHash(String key, String hashKey, Class<T> clazz) {
        if (StrUtil.isEmpty(key)) return null;
        Object object = stringRedisTemplate.opsForHash().get(key, hashKey);
        if (object == null) return null;
        try {
            return JSONObject.parseArray((String) object, clazz);
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        return null;
    }

    @Override
    public void delOpsHash(String key, String hashKey) {
        if (StrUtil.isEmpty(key)) return;
        stringRedisTemplate.opsForHash().delete(key, hashKey);
    }

    public Boolean execLua(String key, String token) {
        String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long resInt = redisTemplateString.execute(redisScript, Collections.singletonList(key), token);
        return 0 == resInt;
    }
}
